package me.leon.ext

import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.core.content.FileProvider
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.util.*
import java.util.concurrent.Executors
import me.leon.ext.android.LanguageUtils
import me.leon.ext.android.SPUtils
import me.leon.ext.android.ShellUtils

/** <pre> blog : http://blankj.com time : 16/12/08 desc : utils about initialization </pre> * */
object Utils {

    class ActivityLifecycleImpl : ActivityLifecycleCallbacks {

        val mActivityList = LinkedList<Activity>()
        private val mStatusListenerMap: MutableMap<Any, OnAppStatusChangedListener> = mutableMapOf()
        private val mDestroyedListenerMap:
            MutableMap<Activity, MutableSet<OnActivityDestroyedListener>> =
            mutableMapOf()

        private var mForegroundCount = 0
        private var mConfigCount = 0
        private var mIsBackground = false

        var topActivity: Activity?
            get() {
                if (mActivityList.isNotEmpty()) {
                    for (i in mActivityList.indices.reversed()) {
                        var activity = mActivityList[i]
                        if (activity == null ||
                                activity.isFinishing ||
                                Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
                                    activity.isDestroyed
                        ) {
                            continue
                        }
                        return activity
                    }
                }
                val topActivityByReflect = topActivityByReflect
                if (topActivityByReflect != null) {
                    topActivity = topActivityByReflect
                }
                return topActivityByReflect
            }
            private set(activity) {

                if (mActivityList.contains(activity)) {
                    if (mActivityList.last != activity) {
                        mActivityList.remove(activity)
                        mActivityList.addLast(activity)
                    }
                } else {
                    mActivityList.addLast(activity)
                }
            }

        private val topActivityByReflect: Activity?
            get() {
                try {
                    @SuppressLint("PrivateApi")
                    val activityThreadClass = Class.forName("android.app.ActivityThread")
                    val currentActivityThreadMethod =
                        activityThreadClass.getMethod("currentActivityThread").invoke(null)
                    val mActivityListField = activityThreadClass.getDeclaredField("mActivityList")
                    mActivityListField.isAccessible = true
                    val activities =
                        mActivityListField.get(currentActivityThreadMethod) as Map<*, *>
                    for (activityRecord in activities.values) {
                        val activityRecordClass = activityRecord!!.javaClass
                        val pausedField = activityRecordClass.getDeclaredField("paused")
                        pausedField.isAccessible = true
                        if (!pausedField.getBoolean(activityRecord)) {
                            val activityField = activityRecordClass.getDeclaredField("activity")
                            activityField.isAccessible = true
                            return activityField.get(activityRecord) as Activity
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }

                return null
            }

        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            LanguageUtils.applyLanguage(activity)
            setAnimatorsEnabled()
            topActivity = activity
        }

        override fun onActivityStarted(activity: Activity) {
            if (!mIsBackground) {
                topActivity = activity
            }
            if (mConfigCount < 0) {
                ++mConfigCount
            } else {
                ++mForegroundCount
            }
        }

        override fun onActivityResumed(activity: Activity) {
            topActivity = activity
            if (mIsBackground) {
                mIsBackground = false
                postStatus(true)
            }
            processHideSoftInputOnActivityDestroy(activity, false)
        }

        override fun onActivityPaused(activity: Activity) {
            // if needed
        }

        override fun onActivityStopped(activity: Activity) {
            if (activity.isChangingConfigurations) {
                --mConfigCount
            } else {
                --mForegroundCount
                if (mForegroundCount <= 0) {
                    mIsBackground = true
                    postStatus(false)
                }
            }
            processHideSoftInputOnActivityDestroy(activity, true)
        }

        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            /**/
        }

        override fun onActivityDestroyed(activity: Activity) {
            mActivityList.remove(activity)
            consumeOnActivityDestroyedListener(activity)
            fixSoftInputLeaks(activity.window)
        }

        fun addOnAppStatusChangedListener(obj: Any, listener: OnAppStatusChangedListener) {
            mStatusListenerMap[obj] = listener
        }

        fun removeOnAppStatusChangedListener(obj: Any) {
            mStatusListenerMap.remove(obj)
        }

        fun removeOnActivityDestroyedListener(activity: Activity?) {
            if (activity == null) return
            mDestroyedListenerMap.remove(activity)
        }

        fun addOnActivityDestroyedListener(
            activity: Activity?,
            listener: OnActivityDestroyedListener?
        ) {
            if (activity == null || listener == null) return
            val listeners: MutableSet<OnActivityDestroyedListener>?
            if (!mDestroyedListenerMap.containsKey(activity)) {
                listeners = HashSet()
                mDestroyedListenerMap[activity] = listeners
            } else {
                listeners = mDestroyedListenerMap[activity]
                if (listeners!!.contains(listener)) return
            }
            listeners.add(listener)
        }

        /**
         * To solve close keyboard when activity onDestroy. The preActivity set windowSoftInputMode
         * will prevent the keyboard from closing when curActivity onDestroy.
         */
        private fun processHideSoftInputOnActivityDestroy(activity: Activity, isSave: Boolean) {
            if (isSave) {
                val attrs = activity.window.attributes
                val softInputMode = attrs.softInputMode
                activity.window.decorView.setTag(-123, softInputMode)
                activity.window.setSoftInputMode(
                    WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
                )
            } else {
                val tag = activity.window.decorView.getTag(-123)
                if (tag !is Int) return
                runOnUiThreadDelayed({ activity.window.setSoftInputMode(tag) }, 100)
            }
        }

        private fun postStatus(isForeground: Boolean) {
            if (mStatusListenerMap.isEmpty()) return
            for (onAppStatusChangedListener in mStatusListenerMap.values) {
                if (isForeground) {
                    onAppStatusChangedListener.onForeground()
                } else {
                    onAppStatusChangedListener.onBackground()
                }
            }
        }

        private fun consumeOnActivityDestroyedListener(activity: Activity) {
            val iterator = mDestroyedListenerMap.entries.iterator()
            while (iterator.hasNext()) {
                val entry = iterator.next()
                if (entry.key === activity) {
                    val value = entry.value
                    for (listener in value) {
                        listener.onActivityDestroyed(activity)
                    }
                    iterator.remove()
                }
            }
        }
    }

    class LeonFileProvider : FileProvider() {
        override fun onCreate(): Boolean {
            init(context)
            return true
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // interface
    ///////////////////////////////////////////////////////////////////////////

    abstract class Task<Result>(private val mCallback: Callback<Result>) : Runnable {

        @Volatile private var state = NEW

        val isDone: Boolean
            get() = state != NEW

        val isCanceled: Boolean
            get() = state == CANCELLED

        abstract fun doInBackground(): Result

        override fun run() {
            try {
                val t = doInBackground()

                if (state != NEW) return
                state = COMPLETING
                UTIL_HANDLER.post { mCallback.onCall(t) }
            } catch (ignored: Throwable) {
                if (state != NEW) return
                state = EXCEPTIONAL
            }
        }

        fun cancel() {
            state = CANCELLED
        }

        companion object {

            private const val NEW = 0
            private const val COMPLETING = 1
            private const val CANCELLED = 2
            private const val EXCEPTIONAL = 3
        }
    }

    interface Callback<T> {
        fun onCall(data: T)
    }

    interface OnAppStatusChangedListener {
        fun onForeground()

        fun onBackground()
    }

    interface OnActivityDestroyedListener {
        fun onActivityDestroyed(activity: Activity)
    }

    @JvmStatic val activityLifecycle = ActivityLifecycleImpl()
    private val UTIL_POOL = Executors.newFixedThreadPool(3)
    private val UTIL_HANDLER = Handler(Looper.getMainLooper())

    @SuppressLint("StaticFieldLeak") private var sApplication: Application? = null

    /**
     * Init utils.
     *
     * Init it in the class of Application.
     *
     * @param context context
     */
    @JvmStatic
    fun init(context: Context?) {
        if (context == null) {
            init(applicationByReflect)
            return
        }
        init(context.applicationContext as Application)
    }

    /**
     * Init utils.
     *
     * Init it in the class of Application.
     *
     * @param app application
     */
    @JvmStatic
    fun init(app: Application?) {
        if (sApplication == null) {
            sApplication = app ?: applicationByReflect
            sApplication!!.registerActivityLifecycleCallbacks(activityLifecycle)
        } else {
            if (app != null && app.javaClass != sApplication!!.javaClass) {
                sApplication!!.unregisterActivityLifecycleCallbacks(activityLifecycle)
                activityLifecycle.mActivityList.clear()
                sApplication = app
                sApplication!!.registerActivityLifecycleCallbacks(activityLifecycle)
            }
        }
    }

    /**
     * Return the context of Application object.
     *
     * @return the context of Application object
     */
    @JvmStatic
    val app: Application
        get() {
            if (sApplication != null) return sApplication as Application
            val app = applicationByReflect
            init(app)
            return app
        }

    @JvmStatic
    val activityList: LinkedList<Activity>
        get() = activityLifecycle.mActivityList

    @JvmStatic
    val topActivityOrApp: Context
        get() {
            if (isAppForeground) {
                val topActivity = activityLifecycle.topActivity
                return topActivity ?: app
            } else {
                return app
            }
        }

    @JvmStatic
    val isAppForeground: Boolean
        get() {
            val am = app.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
            val info = am.runningAppProcesses
            if (info.isNullOrEmpty()) return false
            for (aInfo in info) {
                if (aInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                ) {
                    if (aInfo.processName == app.packageName) {
                        return true
                    }
                }
            }
            return false
        }

    @JvmStatic
    fun doAsync(task: Task<ShellUtils.CommandResult?>) {
        UTIL_POOL.execute(task)
    }

    @JvmStatic
    fun runOnUiThread(runnable: Runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            runnable.run()
        } else {
            UTIL_HANDLER.post(runnable)
        }
    }

    @JvmStatic
    fun runOnUiThreadDelayed(runnable: Runnable, delayMillis: Long) {
        UTIL_HANDLER.postDelayed(runnable, delayMillis)
    }

    @JvmStatic
    val currentProcessName: String
        get() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return Application.getProcessName()
            var name = currentProcessNameByFile
            if (!TextUtils.isEmpty(name)) return name
            name = currentProcessNameByAms
            if (!TextUtils.isEmpty(name)) return name
            name = currentProcessNameByReflect
            return name
        }

    @JvmStatic
    fun fixSoftInputLeaks(window: Window) {
        val imm = app.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        val leakViews = arrayOf("mLastSrvView", "mCurRootView", "mServedView", "mNextServedView")
        for (leakView in leakViews) {
            try {
                val leakViewField = InputMethodManager::class.java.getDeclaredField(leakView)
                if (!leakViewField.isAccessible) {
                    leakViewField.isAccessible = true
                }
                val obj = leakViewField.get(imm)
                if (obj !is View) continue
                if (obj.rootView === window.decorView.rootView) {
                    leakViewField.set(imm, null)
                }
            } catch (ignore: Throwable) {
                /**/
            }
        }
    }

    @JvmStatic
    val spUtils4Utils: SPUtils
        get() = SPUtils.getInstance("Utils")

    ///////////////////////////////////////////////////////////////////////////
    // private method
    ///////////////////////////////////////////////////////////////////////////

    private val currentProcessNameByFile: String
        get() =
            try {
                val file = File("/proc/" + android.os.Process.myPid() + "/" + "cmdline")
                val mBufferedReader = BufferedReader(FileReader(file))
                val processName = mBufferedReader.readLine().trim { it <= ' ' }
                mBufferedReader.close()
                processName
            } catch (e: Exception) {
                e.printStackTrace()
                ""
            }

    private val currentProcessNameByAms: String
        get() {
            val am = app.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
            val info = am.runningAppProcesses
            if (info.isNullOrEmpty()) return ""
            val pid = android.os.Process.myPid()
            for (aInfo in info) {
                if (aInfo.pid != pid) continue
                if (aInfo.processName != null) {
                    return aInfo.processName
                }
            }
            return ""
        }

    private val currentProcessNameByReflect: String
        get() {
            var processName = ""
            try {
                val loadedApkField = app.javaClass.getField("mLoadedApk")
                loadedApkField.isAccessible = true
                val loadedApk = loadedApkField.get(app)

                val activityThreadField = loadedApk.javaClass.getDeclaredField("mActivityThread")
                activityThreadField.isAccessible = true
                val activityThread = activityThreadField.get(loadedApk)

                val getProcessName = activityThread.javaClass.getDeclaredMethod("getProcessName")
                processName = getProcessName.invoke(activityThread) as String
            } catch (e: Exception) {
                e.printStackTrace()
            }

            return processName
        }

    private val applicationByReflect: Application
        get() {
            try {
                @SuppressLint("PrivateApi")
                val activityThread = Class.forName("android.app.ActivityThread")
                val thread = activityThread.getMethod("currentActivityThread").invoke(null)
                val app =
                    activityThread.getMethod("getApplication").invoke(thread)
                        ?: throw NullPointerException("u should init first")
                return app as Application
            } catch (e: Exception) {
                e.printStackTrace()
            }

            throw NullPointerException("u should init first")
        }

    /** Set animators enabled. */
    @SuppressLint("SoonBlockedPrivateApi")
    private fun setAnimatorsEnabled() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ValueAnimator.areAnimatorsEnabled()) {
            return
        }

        try {
            val sDurationScaleField = ValueAnimator::class.java.getDeclaredField("sDurationScale")
            sDurationScaleField.isAccessible = true
            val sDurationScale = sDurationScaleField.get(null) as Float
            if (sDurationScale == 0f) {
                sDurationScaleField.set(null, 1f)
                Log.i("Utils", "setAnimatorsEnabled: Animators are enabled now!")
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
